package me.keiwu.task.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import me.keiwu.pub.data.Response;
import me.keiwu.pub.data.ConfigType;
import me.keiwu.pub.data.ResponseV2;
import me.keiwu.pub.data.config.ChallengeConfigBean;
import me.keiwu.pub.data.enums.NoticeType;
import me.keiwu.pub.data.service.UserInfoBean;
import me.keiwu.pub.service.WXService;
import me.keiwu.pub.util.DateUtil;
import me.keiwu.pub.util.CacheUtil;
import me.keiwu.task.dao.ChallengeDao;
import me.keiwu.task.domain.Challenge;
import me.keiwu.task.rpc.ConfigRpc;
import me.keiwu.task.rpc.NoticeRpc;
import me.keiwu.task.rpc.UserRpc;
import me.keiwu.task.service.ChallengeService;
import me.keiwu.task.util.ExceptionUtil;
import me.keiwu.task.util.bean.Achievement;
import me.keiwu.task.util.enums.ChallengeState;
import me.keiwu.task.util.enums.InviteState;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.*;

/**
 * Created by kei on 13/11/2017.
 */
@Service
@Transactional
public class ChallengeServiceImpl implements ChallengeService {

    private static final Logger logger = LoggerFactory.getLogger(ChallengeServiceImpl.class);


	@Autowired
	private CacheUtil cacheUtil;
	@Autowired
	private ChallengeDao challengeDao;
	@Autowired
	private UserRpc userRpc;
	@Autowired
	private ConfigRpc configRpc;
	@Autowired
	private ExceptionUtil exceptionUtil;
	@Autowired
	private WXService wxServiceImpl;
    @Autowired
    private NoticeRpc noticeRpc;



    @Override
    public Challenge getByChallengeId(Long challengeId) {
        if (challengeId == null) return null;
        return challengeDao.findOne(challengeId);
    }

    @Override
    public Achievement getAchievement(Long userId) {
        if (userId == null) return null;
        List<Challenge> allChallenges = this.findAllUserEndChallenges(userId);
        int win = 0, lose = 0, draw = 0;
        for (Challenge challenge : allChallenges) {
            if (challenge.getInitiator() == null || challenge.getChallenger() == null) continue;
            if (Objects.equals(challenge.getInitiatorStep(), challenge.getChallengerStep())) {
                draw++;
            } else if (userId.equals(challenge.getInitiator())) {
                if (challenge.getInitiatorStep() > challenge.getChallengerStep()) {
                    win++;
                } else {
                    lose++;
                }
            } else {
                if (challenge.getChallengerStep() > challenge.getInitiatorStep()) {
                    win++;
                } else {
                    lose++;
                }

            }
        }
        Achievement achievement = new Achievement();
        achievement.setWin(win);
        achievement.setLose(lose);
        achievement.setDraw(draw);
        double total = win + lose + draw;
        if (total > 0) {
            double rate = new BigDecimal(win / total)
                    .setScale(4, BigDecimal.ROUND_HALF_UP)
                    .doubleValue();
            achievement.setRate(rate);
        }
        return achievement;
    }

    @Override
    public Page<Challenge> findUserStartingChallenges(Long userId, int page, int size) {
        Specification<Challenge> spec = (root, query, cb) -> {
            Predicate p1 = cb.equal(root.get("initiator"), userId);
            Predicate p2 = cb.equal(root.get("challenger"), userId);
            Predicate p3 = cb.equal(root.get("isDel"), false);

            // 要还没结束
            Predicate p4 = cb.lessThan(root.get("state"), ChallengeState.END.getState());
            Predicate p5 = cb.not(cb.lessThanOrEqualTo(root.get("endDate"), new Date()));

            return cb.and(cb.or(p1, p2), p3, p4, p5);
        };
        Sort sort = new Sort(Sort.Direction.DESC, "lastModifiedTime");
        Pageable pageable = new PageRequest(page, size, sort);
        return challengeDao.findAll(spec, pageable);
    }

    @Override
    public Page<Challenge> findUserChallenges(Long userId, int page, int size) {
        Specification<Challenge> spec = (root, query, cb) -> {
            Predicate p1 = cb.equal(root.get("initiator"), userId);
            Predicate p2 = cb.equal(root.get("challenger"), userId);
            Predicate p3 = cb.equal(root.get("isDel"), false);
            return cb.and(cb.or(p1, p2), p3);
        };
        Sort sort = new Sort(Sort.Direction.DESC, "lastModifiedTime");
        Pageable pageable = new PageRequest(page, size, sort);
        return challengeDao.findAll(spec, pageable);
    }

    @Override
    public Page<Challenge> findUserEndChallenges(Long userId, int page, int size) {
        Sort sort = new Sort(Sort.Direction.DESC, "lastModifiedTime");
        Pageable pageable = new PageRequest(page, size, sort);
        return challengeDao.findAll(this.genUserEndChallengesSpecification(userId), pageable);
    }

    @Override
    public List<Challenge> findAllUserEndChallenges(Long userId) {
        return challengeDao.findAll(this.genUserEndChallengesSpecification(userId));
    }

    private Specification<Challenge> genUserEndChallengesSpecification(Long userId) {
        return (root, query, cb) -> {
            Predicate p1 = cb.equal(root.get("initiator"), userId);
            Predicate p2 = cb.equal(root.get("challenger"), userId);
            Predicate p3 = cb.equal(root.get("isDel"), false);

            Predicate p4 = cb.equal(root.get("state"), ChallengeState.END.getState());
            Predicate p5 = cb.equal(root.get("state"), ChallengeState.OVERTIME.getState());
            Predicate p6 = cb.lessThanOrEqualTo(root.get("endDate"), new Date());

            return cb.and(cb.or(p1, p2), p3, cb.or(p4, p5, p6));
        };
    }


    @Override
    public Page<Challenge> findOtherChallenges(Long userId, int page, int size) {
        Specification<Challenge> spec = (root, query, cb) -> {
            Predicate p1 = cb.notEqual(root.get("initiator"), userId);
            Predicate p2 = cb.notEqual(root.get("challenger"), userId);
            Predicate p3 = cb.isNull(root.get("challenger"));
            Predicate p4 = cb.equal(root.get("isDel"), false);

            // 要还没结束
            Predicate p5 = cb.equal(root.get("state"), ChallengeState.STARTING.getState());
            Predicate p6 = cb.equal(root.get("state"), ChallengeState.END.getState());

            return cb.and(p1, cb.or(p2,p3), p4, cb.or(p5, p6));
        };
        Sort.Order order1 = new Sort.Order(Sort.Direction.ASC, "state");
        Sort.Order order2 = new Sort.Order(Sort.Direction.DESC, "lastModifiedTime");
        Sort sort = new Sort(order1, order2);
        Pageable pageable = new PageRequest(page, size, sort);
        return challengeDao.findAll(spec, pageable);
    }



    // TODO
    // 模糊匹配性能问题
    @Override
    public Page<Challenge> findAllChallenges(Integer state, Date afterDate, Date beforeDate, String name, int page, int size) {
        Specification<Challenge> spec = (root, query, cb) -> {
            Predicate res = cb.equal(root.get("isDel"), false);
            if (state != null) {
                res = cb.and(res, cb.equal(root.get("state"), state));
            }
            if (afterDate != null) {
                res = cb.and(res, cb.not(cb.lessThan(root.get("startDate"), afterDate)));
            }
            if (beforeDate != null) {
                res = cb.and(res, cb.lessThanOrEqualTo(root.get("endDate"), beforeDate));
            }
            if (StringUtils.isNotBlank(name)) {
                // 性能问题
                res = cb.and(res, cb.like(root.get("name"), MessageFormat.format("%{0}%", name)));
            }
            return res;
        };
        Sort sort = new Sort(Sort.Direction.DESC, "lastModifiedTime");
        Pageable pageable = new PageRequest(page, size, sort);
        return challengeDao.findAll(spec, pageable);
    }

    @Override
    public Page<Challenge> findAllStartingChallenges(int page, int size) {
        Specification<Challenge> spec = (root, query, cb) -> {
            // 要还没结束
            Predicate p1 = cb.lessThan(root.get("state"), ChallengeState.END.getState());
            Predicate p2 = cb.lessThanOrEqualTo(root.get("endDate"), new Date());
            Predicate p3 = cb.equal(root.get("isDel"), false);
            return cb.and(p1, p2, p3);
        };
        Sort sort = new Sort(Sort.Direction.DESC, "lastModifiedTime");
        Pageable pageable = new PageRequest(page, size, sort);
        return challengeDao.findAll(spec, pageable);
    }

    @Override
    public Challenge closeChallenge(Long challengeId) {
        Challenge challenge = challengeDao.findOne(challengeId);
        if (challenge == null) return null;
        challenge.setState(ChallengeState.CLOSE.getState());
        challengeDao.save(challenge);
        return challenge;
    }

    @Override
    public Challenge reopenChallenge(Long challengeId) {
        Challenge challenge = challengeDao.findOne(challengeId);
        if (challenge == null || challenge.getState() != ChallengeState.CLOSE.getState()) return null;
        // 重开
        int state = ChallengeState.STARTING.getState();
        if (challenge.getChallenger() == null) state = ChallengeState.INIT.getState();
        challenge.setState(state);
        challengeDao.save(challenge);
        return challenge;
    }

    
    @Override
    public Challenge createChallenge(Long userId, Long challenger, String name, Date startDate, Date endDate) {
        ChallengeConfigBean taskConfig = this.getChallengeTaskConfig();
        if (taskConfig == null) return null;
        // 判断时间
        if (DateUtil.getMinuteOfDay(new Date()) > taskConfig.getStopChallengeTime()) return null;
        if (userRpc.changeGold(userId, -Math.abs(taskConfig.getChallengeCostGold()))) {
            Challenge challenge = new Challenge();
            // 设置头像
            UserInfoBean userBean = this.getUserInfo(userId);
            if (userBean != null) {
                challenge.setInitiatorName(userBean.getNickName());
                challenge.setInitiatorAvatar(userBean.getWxAvatarUrl());
            }
            if (challenger != null) {
                // 只设置头像名字，不设置id
                UserInfoBean challengerBean = this.getUserInfo(challenger);
                if (challengerBean != null) {
                    challenge.setChallengerName(challengerBean.getNickName());
                    challenge.setChallengerAvatar(challengerBean.getWxAvatarUrl());
                }
            }

            // 设置步数
            int startDays = DateUtil.getDayStrInt(startDate);
            int endDays = DateUtil.getDayStrInt(endDate);
            int steps = userRpc.getUserStepsScope(userId, startDays, endDays);

            challenge.setName(name);
            challenge.setInitiator(userId);
            challenge.setInitiatorStep(steps);
            challenge.setStartDate(startDate);
            challenge.setEndDate(endDate);
            challenge.setState(ChallengeState.INIT.getState());
            challengeDao.save(challenge);


            if (challenger != null) {
                // 邀请通知
                this.challengeNotice(userId, challenger, challenge, InviteState.INIT.getState(), true);
                this.challengeNotice(challenger, userId, challenge, InviteState.INIT.getState(), false);
            }

            return challenge;
        }
        return null;
    }

    @Override
    public Response replyChallenge(Long challengeId, Long invitee, int state) {
        Challenge challenge = challengeDao.findOne(challengeId);

        if (challenge == null || challenge.getState() != ChallengeState.INIT.getState() ||
                (challenge.getEndDate() != null && challenge.getEndDate().before(new Date()))) {
            return Response.fail("挑战已过期");
        }


        if (invitee.equals(challenge.getInitiator())) {
            return Response.fail("不能挑战自己");
        }

        // 通知
        this.challengeNotice(invitee, challenge.getInitiator(), challenge, state, false);
        this.challengeNotice(challenge.getInitiator(), invitee, challenge, state, true);

		// 加入挑战
		ChallengeConfigBean taskConfig = this.getChallengeTaskConfig();
		if (taskConfig == null)
			return Response.fail("服务器错误");
		if (InviteState.ACCEPT.getState() == state) {
            if (!this.checkCanChallenge(invitee, challenge.getInitiator(), challenge.getStartDate(), challenge.getEndDate()))
                return Response.fail("你与该用户已有一场正在进行中的挑战，不可重复对战哦");
			if (userRpc.changeGold(invitee, -Math.abs(taskConfig.getChallengeCostGold()))) {
			    Challenge resultChallenge = this.challenge(challenge, invitee);
                if (isTimeForTmpMsg()) {
                    // 加入挑战，回复消息给邀请者
                    Map<String, Object> map = new HashMap<String, Object>();
                    String fromUser = userRpc.userInfo(resultChallenge.getChallenger());
                    String toUser = userRpc.userInfo(resultChallenge.getInitiator());
                    String userFormStr = userRpc.getUserForm(resultChallenge.getInitiator());
                    exceptionUtil.logError(this.getClass(),"replyChallenge tmpmsg", "userFormStr:" + userFormStr);
                    if (userFormStr != null){
                        Map<String, Object> formMap = JSON.parseObject(userFormStr, Map.class);
                        String formId = (String) formMap.get("formId");
                        Long form_db_id = ((Number)formMap.get("id")).longValue();
                        String page = "pages/task/task-details/task-details?challengeId="+resultChallenge.getId();
                        map.put("formId", formId);
                        map.put("templateMsgType",0);
                        // 调用发送模板 传参发送消息
                        // 0 表示 CHALLENGE_ACCEPT 好友接受挑战通知
                        exceptionUtil.logError(this.getClass(),"replyChallenge tmpmsg", "replyChallenge param:" + map.toString());
                        exceptionUtil.logError(this.getClass(),"replyChallenge tmpmsg", "page:"+page);
                        ResponseV2 resp = wxServiceImpl.sendTemplateMsg(fromUser, toUser, page, map);
                        if(resp.getCode() == HttpStatus.OK.value()) {
                            userRpc.deleteUsedForm(form_db_id);
                        }else if (resp.getCode() == 1) {
                            userRpc.deleteUsedForm(form_db_id);
                        }else {
                            exceptionUtil.logError(this.getClass(),"replyChallenge tmpmsg", resp.getMsg());
                        }

                    }
                }
				return Response.ok(resultChallenge);
			}
			return Response.fail("星币不足");
		} else if (InviteState.REFUSE.getState() == state && StringUtils.isNotBlank(challenge.getChallengerAvatar())) {
			// 拒绝单点邀请的，直接让任务过期
			challenge.setState(ChallengeState.OVERTIME.getState());
			challengeDao.save(challenge);
		}
		return Response.ok();
	}

    private boolean isTimeForTmpMsg() {
        Calendar rightNow = Calendar.getInstance();
        int hour = rightNow.get(Calendar.HOUR_OF_DAY);
        return (hour > 8 && hour <23);
    }

    /**
     * 挑战某人
     * @param challenge
     * @param challenger
     */
    private Challenge challenge(Challenge challenge, Long challenger) {
        // 设置步数
        challenge.setChallenger(challenger);
        int startDays = DateUtil.getDayStrInt(challenge.getStartDate());
        int endDays = DateUtil.getDayStrInt(challenge.getEndDate());
        int initiatorStep = userRpc.getUserStepsScope(challenge.getInitiator(), startDays, endDays);
        int challengeStep = userRpc.getUserStepsScope(challenger, startDays, endDays);

        logger.info("challenge challengeId={}, initiatorStep={}, challengeStep={}", challenge.getId(), initiatorStep, challengeStep);

        challenge.setInitiatorStep(initiatorStep);
        challenge.setChallengerStep(challengeStep);
        challenge.setState(ChallengeState.STARTING.getState());

        // 设置头像
        UserInfoBean userInfo = cacheUtil.getLoginUserInfo(challenger);
        if (userInfo == null) {
            String info = userRpc.userInfo(challenger);
            userInfo = JSON.parseObject(info, UserInfoBean.class);
        }
        if (userInfo != null) {
            challenge.setChallengerName(userInfo.getNickName());
            challenge.setChallengerAvatar(userInfo.getWxAvatarUrl());
        }

        challengeDao.save(challenge);
        return challenge;
    }

    @Override
    public Response awardChallenge(Long userId, Long challengeId) {
        Challenge challenge = challengeDao.findOne(challengeId);
        int awardGold = this.getAwardGold(userId, challenge);
        if (awardGold <= 0) return Response.ok();
        if (userRpc.changeGold(userId, awardGold)) {
            challengeDao.save(challenge);

            if (!cacheUtil.isAwardChallenge(userId, challengeId)) {
                cacheUtil.recordUserAwardForChallenge(userId, challengeId);
            }

            return Response.ok(awardGold);
        } else {
            logger.error("change gold in award fail, userId={}, awardGold={}, challengeId={}", userId, awardGold, challengeId);
            exceptionUtil.logError(this.getClass(), "awardChallenge", "change gold fail1: user="
                    + userId + " gold=" + awardGold + " id=" + challengeId);
            return Response.ok();
        }
    }

    @Override
    public Response awardAllChallenge(Long userId) {
        List<Challenge> challenges = challengeDao.findAllNotAward(userId);
        int totalGold = 0;
        for (Challenge c : challenges) {
            int awardGold = this.getAwardGold(userId, c);
            if (awardGold <= 0)  continue;
            if (userRpc.changeGold(userId, awardGold)) {
                challengeDao.save(c);
                totalGold += awardGold;
            } else {
                logger.error("change gold in awardAll fail, userId={}, awardGold={}, challengeId={}", userId, awardGold, c.getId());
                exceptionUtil.logError(this.getClass(), "awardAllChallenge", "change gold fail2: user="
                        + userId + " gold=" + awardGold + " id=" + c.getId());
            }
        }
        if (cacheUtil.canTemplateMsgForResult(userId)) {
            cacheUtil.recordTemplateMsgForResult(userId);
        }
        return Response.ok(totalGold);
    }


    private int getAwardGold(Long userId, Challenge challenge) {
        if (challenge == null || challenge.isDel() || challenge.isAwarded()) return 0;

        // 获取配置
        ChallengeConfigBean taskConfig = this.getChallengeTaskConfig();
        if (taskConfig == null) {
            logger.error("can not get config!");
            exceptionUtil.logError(this.getClass(), "getAwardGold", "can not get config");
            return 0;
        }
        int awardGold = Math.abs(taskConfig.getAwardGold()) + 2 * Math.abs(taskConfig.getChallengeCostGold());
        int returnGold = Math.abs(taskConfig.getChallengeCostGold());

        if (ChallengeState.OVERTIME.getState() == challenge.getState()) {
            challenge.setAwarded(true);
            return returnGold;
        }

        // 还没结束，不能奖励
        if (new Date().before(challenge.getEndDate())) return 0;
        // 关闭了没有奖励
        if (ChallengeState.CLOSE.getState() == challenge.getState()) return 0;


        // 失效
        if (challenge.getChallenger() == null) {
            challenge.setState(ChallengeState.OVERTIME.getState());
            challenge.setAwarded(true);
            return returnGold;
        }

        // 胜利
        int initStep = challenge.getInitiatorStep();
        int challengeStep = challenge.getChallengerStep();
        if (userId.equals(challenge.getInitiator()) && initStep > challengeStep
                || userId.equals(challenge.getChallenger()) && initStep < challengeStep) {
            challenge.setState(ChallengeState.END.getState());
            challenge.setAwarded(true);
            return awardGold;
        }

        // 输了
        return 0;
    }


    @Override
    public Response triggerUpdateStep(long userId) {
        List<Challenge> list = challengeDao.findRelatedStartingChallenge(userId);
        list.forEach(c -> {
            int startDays = DateUtil.getDayStrInt(c.getStartDate());
            int endDays = DateUtil.getDayStrInt(c.getEndDate());
            int steps = userRpc.getUserStepsScope(userId, startDays, endDays);

            if (userId == c.getInitiator()) {
                c.setInitiatorStep(steps);
            } else if (userId == c.getChallenger()) {
                c.setChallengerStep(steps);
            }
        });
        challengeDao.save(list);
        return Response.ok();
    }


    // TODO
    // 模糊匹配，搜素引擎
    @Override
    public Page<Challenge> findChallengesByName(String name, int page, int size) {
        Specification<Challenge> spec = (root, query, cb) -> {
            return cb.like(root.get("name"), MessageFormat.format("%{0}%", name));
        };
        Sort sort = new Sort(Sort.Direction.DESC, "createdTime");
        Pageable pageable = new PageRequest(page, size, sort);

        return challengeDao.findAll(spec, pageable);
    }


    /**
     * 获取挑战任务配置
     * @return
     */
    private ChallengeConfigBean getChallengeTaskConfig() {
        int configType = ConfigType.CHALLENGE_CONFIG.getType();
        ChallengeConfigBean taskConfig = cacheUtil.getConfig(ConfigType.CHALLENGE_CONFIG, ChallengeConfigBean.class);
        if (taskConfig == null) {
            String config = configRpc.getConfig(configType);
            if (StringUtils.isBlank(config)) {
                logger.error("can not fetch the task config: {}", configType);
                exceptionUtil.logError(this.getClass(), "getChallengeTaskConfig", "can not fetch the task config");
                return null;
            }
            cacheUtil.setConfig(configType, config);
        }
        return taskConfig;
    }




    private UserInfoBean getUserInfo(Long userId) {
        UserInfoBean userInfo = cacheUtil.getLoginUserInfo(userId);
        if (userInfo == null) {
            String info = userRpc.userInfo(userId);
            userInfo = JSON.parseObject(info, UserInfoBean.class);
        }
        return userInfo;
    }


    @Override
    public Integer countAllChallenges(Long userId) {
        return challengeDao.countAllByUserId(userId);
    }

    @Override
    public Integer countStartingChallenges(Long userId) {
        return challengeDao.countStartingByUserId(userId);
    }


    @Override
    public boolean checkHasChallenge(Long userId, Long otherId) {
        return challengeDao.countByUserIdAndOtherId(userId, otherId) > 0;
    }


    @Override
    public boolean checkCanChallenge(Long userId, Long otherId, Date startDate, Date endDate) {
        List<Challenge> list = challengeDao.findRelatedStartingChallenge(userId, otherId);
        for (Challenge challenge : list) {
            if (DateUtil.isSameDay(challenge.getStartDate(), startDate) && DateUtil.isSameDay(challenge.getEndDate(), endDate))
                return false;
        }
        return true;
    }

    @Override
    public Page<Challenge> findYesterdayChallenge(int page, int size){
        Pageable pg = new PageRequest(page,size);
        return challengeDao.findYesterdayChallenge(pg);
    }

    @Override
    public Challenge findChallengeById(Long challengeId) {
        return challengeDao.findOne(challengeId);
    }


    private void challengeNotice(Long fromUser, Long toUser, Challenge challenge, Integer state, boolean redPoint) {
        JSONObject json = new JSONObject();
        json.put("challengeId", challenge.getId());
        json.put("initiator", challenge.getInitiator());
        json.put("challenger", challenge.getChallenger());
        json.put("startTime", challenge.getStartDate().getTime() / 1000);
        json.put("endTime", challenge.getEndDate().getTime() / 1000);
        json.put("state", state);
        String uniqueKey = challenge.getId() + "";
        noticeRpc.uniqueNotice(NoticeType.CHALLENGE.getType(), uniqueKey, fromUser, toUser, redPoint, json.toJSONString());
    }

}
